use crate::entity::cms_article;
use crate::entity::cms_article_category;
use crate::entity::cms_category;
use crate::entity::cms_topic;
use crate::entity::prelude::*;
use crate::utils::current_datetime;
use ane_macros::model_fetch;
use ane_macros::model_list;
use ane_macros::model_struct;

use sea_orm::prelude::Expr;
use sea_orm::ActiveValue::NotSet;
use sea_orm::ConnectionTrait;
use sea_orm::QuerySelect;
use sea_orm::RelationTrait;
use sea_orm::TransactionTrait;
use sea_orm::{
    ActiveModelTrait, ColumnTrait, DbConn, DerivePartialModel, EntityTrait, FromQueryResult,
    ModelTrait, PaginatorTrait, PartialModelTrait, QueryFilter, QueryOrder, QueryTrait, Set,
};
use serde::Deserialize;
use validator::Validate;

#[derive(Debug, Clone, Deserialize, Validate, Default)]
pub struct ArticleDo {
    pub topic_id: Option<i32>,
    #[validate(length(min = 1, max = 64), non_control_character)]
    pub title: Option<String>,
    pub author: Option<String>,
    pub fromurl: Option<String>,
    pub image: Option<String>,
    pub keywords: Option<String>,
    pub description: Option<String>,
    pub allow_comments: Option<bool>,
    /// 状态
    /// - 0 待发布
    /// - 1 审核中
    /// - 2 已发布
    /// - 3 被驳回
    /// - `>=64` 回收站
    pub status: Option<u8>,
    pub read_number: Option<u32>,
    pub content: Option<String>,
    pub is_markdown: Option<bool>,
    pub publish_at: Option<chrono::NaiveDateTime>,
    pub categories: Option<Vec<i32>>,
}

model_struct!(
    #model=cms_topic
    #[derive(Debug, Clone, Deserialize, Validate)]
    pub struct TopicDo {
        /// 名称
        #[validate(length(min = 2, max = 32), non_control_character)]
        pub name: String,
        /// 描述
        pub description: String,
        /// 图片
        pub image: String,
        /// 图标
        pub icon: String,
        /// 别名
        pub alias: String,
        /// 排序
        pub order: i16
    }
);

/// cms的管里接口
pub struct Manager;

/// 文章相关的操作
#[allow(unused)]
impl Manager {
    model_list!(
        #model=cms_article
        #filter=[Status.lt(64)]
        #order=[Id:Desc]
        /// 列出用户的文章列表
        /// # Params
        /// - user_id 用户id
        /// - title 可选，搜索相似的标题
        /// - topic_id 可选，专题的id
        pub fn list_article(
            UserId.eq(user_id:Option<i32>),
            Title.contains(title: Option<String>),
            TopicId.eq(topic_id:Option<i32>),
            Status.eq(status:Option<i8>),
        )
    );

    model_list!(
        #model=cms_article
        #filter=[Status.gte(64)]
        #order=[Id:Desc]
        /// 列出用户的文章列表
        /// # Params
        /// - user_id 用户id
        /// - title 可选，搜索相似的标题
        /// - topic_id 可选，专题的id
        pub fn list_article_recycle(
            UserId.eq(user_id:Option<i32>),
            Title.contains(title: Option<String>),
            TopicId.eq(topic_id:Option<i32>),
        )
    );

    model_fetch!(
        #model=cms_article
        /// 获得用户的一篇文章
        pub fn fetch_article(UserId.eq(user_id:Option<i32>), Id.eq(id:i32))
    );

    /// 获得一篇文章的分类
    /// - status 是否根据分类的stauts状态进行筛选
    pub async fn fetch_article_category<T>(
        conn: &DbConn,
        id: i32,
        status: Option<u8>,
    ) -> super::Result<Vec<T>>
    where
        T: PartialModelTrait,
    {
        cms_article_category::Entity::find()
            .join(
                sea_orm::JoinType::LeftJoin,
                cms_article_category::Relation::CmsCategory.def(),
            )
            .apply_if(status, |sql, v| {
                sql.filter(cms_category::Column::Status.eq(v))
            })
            .filter(cms_article_category::Column::ArticleId.eq(id))
            .order_by_asc(cms_category::Column::Lt)
            .into_partial_model()
            .all(conn)
            .await
    }

    /// 获得一篇文章的分类id
    /// - status 是否根据分类的stauts状态进行筛选
    pub async fn fetch_article_category_id(
        conn: &DbConn,
        id: i32,
        status: Option<u8>,
    ) -> super::Result<Vec<i32>> {
        cms_article_category::Entity::find()
            .select_only()
            .column(cms_article_category::Column::CategoryId)
            .join(
                sea_orm::JoinType::LeftJoin,
                cms_article_category::Relation::CmsCategory.def(),
            )
            .apply_if(status, |sql, v| {
                sql.filter(cms_category::Column::Status.eq(v))
            })
            .filter(cms_article_category::Column::ArticleId.eq(id))
            .order_by_asc(cms_category::Column::Lt)
            .into_tuple::<i32>()
            .all(conn)
            .await
    }

    pub async fn create_article(
        conn: &DbConn,
        user_id: i32,
        data: ArticleDo,
    ) -> super::Result<cms_article::Model> {
        let mut model = cms_article::ActiveModel {
            topic_id: Set(data.topic_id.unwrap_or(0)),
            title: Set(data.title.unwrap_or("Untitled".to_owned())),
            author: Set(data.author.unwrap_or("".to_owned())),
            fromurl: Set(data.fromurl.unwrap_or("".to_owned())),
            read_number: Set(data.read_number.unwrap_or(0)),
            status: Set(data.status.unwrap_or(0)),
            image: Set(data.image.unwrap_or("".to_owned())),
            is_markdown: Set(data.is_markdown.unwrap_or(true)),
            allow_comments: Set(data.allow_comments.unwrap_or(true)),
            keywords: Set(data.keywords.unwrap_or("".to_owned())),
            description: Set(data.description.unwrap_or("".to_owned())),
            content: Set(data.content.unwrap_or("".to_owned())),
            publish_at: Set(data.publish_at.unwrap_or_else(current_datetime)),
            created_at: Set(current_datetime()),
            updated_at: Set(current_datetime()),
            user_id: Set(user_id),
            ..Default::default()
        };
        let res = model.insert(conn).await?;
        let id = res.id;
        if let Some(c) = data.categories {
            cms_article_category::Entity::delete_many()
                .filter(cms_article_category::Column::ArticleId.eq(id))
                .exec(conn)
                .await?;
            if c.len() > 0 {
                cms_article_category::Entity::insert_many(c.into_iter().map(|c| {
                    cms_article_category::ActiveModel {
                        article_id: Set(id),
                        category_id: Set(c),
                    }
                }))
                .exec(conn)
                .await?;
            }
        }
        Ok(res)
    }

    pub async fn update_article(
        conn: &impl ConnectionTrait,
        user_id: Option<i32>,
        id: i32,
        data: ArticleDo,
    ) -> super::Result<cms_article::Model> {
        let mut model = cms_article::ActiveModel {
            id: Set(id),
            topic_id: data.topic_id.map(Set).unwrap_or(NotSet),
            title: data.title.map(Set).unwrap_or(NotSet),
            author: data.author.map(Set).unwrap_or(NotSet),
            fromurl: data.fromurl.map(Set).unwrap_or(NotSet),
            read_number: data.read_number.map(Set).unwrap_or(NotSet),
            status: data.status.map(Set).unwrap_or(NotSet),
            image: data.image.map(Set).unwrap_or(NotSet),
            is_markdown: data.is_markdown.map(Set).unwrap_or(NotSet),
            allow_comments: data.allow_comments.map(Set).unwrap_or(NotSet),
            keywords: data.keywords.map(Set).unwrap_or(NotSet),
            description: data.description.map(Set).unwrap_or(NotSet),
            content: data.content.map(Set).unwrap_or(NotSet),
            publish_at: data.publish_at.map(Set).unwrap_or(NotSet),
            updated_at: Set(current_datetime()),
            ..Default::default()
        };
        let res = cms_article::Entity::update(model)
            .apply_if(user_id, |sql, v| {
                sql.filter(cms_article::Column::UserId.eq(v))
            })
            .exec(conn)
            .await?;
        if let Some(c) = data.categories {
            cms_article_category::Entity::delete_many()
                .filter(cms_article_category::Column::ArticleId.eq(id))
                .exec(conn)
                .await?;
            if c.len() > 0 {
                cms_article_category::Entity::insert_many(c.into_iter().map(|c| {
                    cms_article_category::ActiveModel {
                        article_id: Set(id),
                        category_id: Set(c),
                    }
                }))
                .exec(conn)
                .await?;
            }
        }
        Ok(res)
    }

    ///  删除用户的文章, 和文章关联的category
    pub async fn del_article(
        conn: &impl ConnectionTrait,
        user_id: Option<i32>,
        id: i32,
    ) -> std::result::Result<(), sea_orm::DbErr> {
        if let Some(art) = cms_article::Entity::find_by_id(id)
            .apply_if(user_id, |sql, v| {
                sql.filter(cms_article::Column::UserId.eq(v))
            })
            .one(conn)
            .await?
        {
            cms_article_category::Entity::delete_many()
                .filter(cms_article_category::Column::ArticleId.eq(art.id))
                .exec(conn)
                .await;
            art.delete(conn).await?;
        }
        Ok(())
    }
}

/// 专题相关的操作
#[allow(unused)]
impl Manager {
    model_list!(
        #model=cms_topic
        /// 列出用户的专题
        pub fn list_topic(UserId.eq(user_id:i32))
    );

    model_fetch!(
        #model=cms_topic
        /// 获得用户的一个专题
        pub fn fetch_topic(UserId.eq(user_id:i32), Id.eq(id:i32))
    );

    /// 创建一个专题
    pub async fn create_topic(
        conn: &DbConn,
        user_id: i32,
        data: TopicDo,
    ) -> super::Result<cms_topic::Model> {
        let mut model: cms_topic::ActiveModel = data.into();
        model.created_at = Set(current_datetime());
        model.updated_at = Set(current_datetime());
        model.user_id = Set(user_id);
        model.insert(conn).await
    }

    /// 更新一个专题
    pub async fn update_topic(
        conn: &DbConn,
        user_id: i32,
        id: i32,
        data: TopicDo,
    ) -> super::Result<cms_topic::Model> {
        let mut model: cms_topic::ActiveModel = data.into();
        model.updated_at = Set(current_datetime());
        model.id = Set(id);
        cms_topic::Entity::update(model)
            .filter(cms_topic::Column::UserId.eq(user_id))
            .exec(conn)
            .await
    }

    /// 删除一个专题
    pub async fn del_topic(conn: &DbConn, user_id: i32, id: i32) -> super::Result<()> {
        let s = cms_topic::Entity::delete_many()
            .filter(cms_topic::Column::Id.eq(id))
            .filter(cms_topic::Column::UserId.eq(user_id))
            .exec(conn)
            .await?;
        cms_article::Entity::update_many()
            .col_expr(cms_article::Column::TopicId, Expr::value(0))
            .filter(cms_article::Column::Id.eq(id))
            .filter(cms_article::Column::UserId.eq(user_id))
            .exec(conn);
        Ok(())
    }
}

#[derive(Debug, Clone, Deserialize, Validate)]
pub struct CategoryDo {
    pub p_id: Option<i32>,
    #[validate(length(min = 1, max = 64), non_control_character)]
    pub name: Option<String>,
    pub description: Option<String>,
    pub icon: Option<String>,
    pub alias: Option<String>,
    pub status: Option<i8>,
    pub sort: Option<u64>,
}

#[derive(DerivePartialModel, FromQueryResult)]
#[sea_orm(entity = "CmsCategory")]
pub struct TreeInfo {
    pub id: i32,
    /// 上级目录
    pub p_id: i32,
    /// 左值
    pub lt: i32,
    /// 右值
    pub rt: i32,
    /// 层级
    pub lv: i32,
}

/// 分类相关的操作
#[allow(unused)]
impl Manager {
    pub async fn fetch_category<T>(conn: &impl ConnectionTrait, id: i32) -> super::Result<Option<T>>
    where
        T: PartialModelTrait + Sync + Send,
    {
        use cms_category::{ActiveModel, Column, Entity};
        cms_category::Entity::find_by_id(id)
            .into_partial_model()
            .one(conn)
            .await
    }

    /// 根据父id查找, 第sort的子节点, 没找到返回 Ok(None)
    #[inline]
    async fn find_sort(
        conn: &impl ConnectionTrait,
        p_id: i32,
        sort: Option<u64>,
    ) -> super::Result<Option<TreeInfo>> {
        use cms_category::{ActiveModel, Column, Entity};
        if let Some(sort) = sort {
            let sort = sort.max(1) - 1;
            Ok(Entity::find()
                .filter(Column::PId.eq(p_id))
                .order_by_asc(Column::Lt)
                .offset(Some(sort))
                .into_partial_model::<TreeInfo>()
                .one(conn)
                .await?)
        } else {
            Ok(None)
        }
    }

    /// 添加一个类别, 当父类不存在时返回Ok(None)
    pub async fn add_category(
        conn: &DbConn,
        data: CategoryDo,
    ) -> super::Result<Option<cms_category::Model>> {
        use cms_category::{ActiveModel, Column, Entity};

        let mut model = ActiveModel {
            p_id: Set(data.p_id.unwrap_or(0)),
            name: Set(data.name.unwrap_or("".to_owned())),
            description: Set(data.description.unwrap_or("".to_owned())),
            icon: Set(data.icon.unwrap_or("".to_owned())),
            alias: Set(data.alias.unwrap_or("".to_owned())),
            status: Set(data.status.unwrap_or(0)),
            created_at: Set(current_datetime()),
            updated_at: Set(current_datetime()),
            lv: Set(1),
            ..Default::default()
        };
        let mut lt = 0;
        let mut rt = 0;
        let mut lv = 1;
        let mut is_last = false;
        let mut p_id = data.p_id.unwrap_or(0);
        let txn = conn.begin().await?;
        if p_id == 0 {
            if let Some(node) = Self::find_sort(&txn, p_id, data.sort).await? {
                lt = node.lt;
                rt = node.lt + 1;
            }
            if rt == lt {
                let count = Entity::find().count(&txn).await?;
                lt = count as i32 * 2 + 1;
                rt = count as i32 * 2 + 2;
                is_last = true;
            }
        } else {
            if let Some(node) = Entity::find_by_id(p_id)
                .into_partial_model::<TreeInfo>()
                .one(&txn)
                .await?
            {
                lt = node.rt;
                rt = node.rt + 1;
                lv = node.lv + 1;
                if let Some(node) = Self::find_sort(&txn, p_id, data.sort).await? {
                    lt = node.lt;
                    rt = node.lt + 1;
                }
            } else {
                return Ok(None);
            };
        }
        if !is_last {
            Entity::update_many()
                .col_expr(Column::Rt, Expr::col(Column::Rt).add(2))
                .filter(Column::Rt.gte(lt))
                .exec(&txn)
                .await?;
            Entity::update_many()
                .col_expr(Column::Lt, Expr::col(Column::Lt).add(2))
                .filter(Column::Lt.gte(lt))
                .exec(&txn)
                .await?;
        }
        model.lt = Set(lt);
        model.rt = Set(rt);
        model.lv = Set(lv);
        let result = model.insert(&txn).await?;
        txn.commit().await?;
        Ok(Some(result))
    }

    pub async fn del_category(conn: &DbConn, id: i32) -> super::Result<()> {
        use cms_category::{ActiveModel, Column, Entity};

        let txn = conn.begin().await?;
        let p = Entity::find_by_id(id).one(&txn).await?.unwrap();
        let delta = p.rt - p.lt + 1;
        Entity::delete_many()
            .filter(Column::Lt.gte(p.lt))
            .filter(Column::Rt.lte(p.rt))
            .exec(&txn)
            .await?;
        Entity::update_many()
            .col_expr(Column::Rt, Expr::col(Column::Rt).sub(delta))
            .filter(Column::Rt.gt(p.rt))
            .exec(&txn)
            .await?;
        Entity::update_many()
            .col_expr(Column::Lt, Expr::col(Column::Lt).sub(delta))
            .filter(Column::Lt.gt(p.lt))
            .exec(&txn)
            .await?;
        txn.commit().await?;
        Ok(())
    }

    pub async fn update_category(conn: &DbConn, id: i32, data: CategoryDo) -> super::Result<()> {
        use cms_category::{ActiveModel, Column, Entity};
        let mut model = ActiveModel {
            p_id: data.p_id.map(Set).unwrap_or(NotSet),
            name: data.name.map(Set).unwrap_or(NotSet),
            description: data.description.map(Set).unwrap_or(NotSet),
            icon: data.icon.map(Set).unwrap_or(NotSet),
            alias: data.alias.map(Set).unwrap_or(NotSet),
            status: data.status.map(Set).unwrap_or(NotSet),
            updated_at: Set(current_datetime()),
            ..Default::default()
        };

        let txn = conn.begin().await?;
        // 查找修改数据是否存在
        let self_model = if let Some(m) = Entity::find_by_id(id).one(&txn).await? {
            m
        } else {
            return Ok(());
        };
        let mut base_r = self_model.rt + 1;
        // 移动后的坐标信息
        let mut lt = self_model.lt;
        let mut rt = self_model.rt;
        let mut lv = self_model.lv;

        model.id = Set(self_model.id);

        let mut change_parent = matches!(data.p_id, Some(x) if x!=self_model.p_id);
        let mut p_id = data.p_id.unwrap_or(self_model.p_id);
        if change_parent {
            if p_id == 0 {
                let count = Entity::find().count(&txn).await?;
                base_r = count as i32 * 2 + 1;
                lv = 1
            } else {
                if let Some(p_node) = Entity::find_by_id(p_id)
                    .filter(Column::Lt.not_between(self_model.lt, self_model.rt))
                    .filter(Column::Rt.not_between(self_model.lt, self_model.rt))
                    .into_partial_model::<TreeInfo>()
                    .one(&txn)
                    .await?
                {
                    base_r = p_node.rt;
                    lv = p_node.lv + 1;
                } else {
                    log::warn!("new parent category {p_id} not found or in circle");
                    return Ok(());
                };
            }
            if let Some(node) = Self::find_sort(&txn, p_id, data.sort).await? {
                base_r = node.lt
            }
        } else {
            if let Some(sort) = data.sort {
                let sort = sort.max(1);
                let nodes = Entity::find()
                    .filter(Column::PId.eq(p_id))
                    .order_by_asc(Column::Lt)
                    .limit(Some(sort))
                    .into_partial_model::<TreeInfo>()
                    .all(&txn)
                    .await?;
                let node = &nodes[nodes.len() - 1];
                if let Some(i) = nodes.iter().position(|f| f.id == self_model.id) {
                    base_r = node.rt + 1;
                } else {
                    base_r = node.lt;
                }
            }
        }

        if base_r > self_model.rt {
            rt = base_r - 1;
            lt = rt - (self_model.rt - self_model.lt);
        } else {
            lt = base_r;
            rt = lt + self_model.rt - self_model.lt
        }
        /// 当坐标需要改变时
        if lt != self_model.lt || rt != self_model.rt {
            // 移动自己到负数
            Entity::update_many()
                .col_expr(Column::Lt, Expr::col(Column::Lt).mul(-1))
                .col_expr(Column::Rt, Expr::col(Column::Rt).mul(-1))
                .filter(Column::Lt.gte(self_model.lt))
                .filter(Column::Rt.lte(self_model.rt))
                .exec(&txn)
                .await?;

            let range_l = lt.min(self_model.lt);
            let range_r = rt.max(self_model.rt);
            let mut delta_self = self_model.rt - self_model.lt + 1;
            if self_model.lt < lt {
                delta_self = -delta_self;
            }
            Entity::update_many()
                .col_expr(Column::Lt, Expr::col(Column::Lt).add(delta_self))
                .filter(Column::Lt.between(range_l, range_r))
                .exec(&txn)
                .await?;
            Entity::update_many()
                .col_expr(Column::Rt, Expr::col(Column::Rt).add(delta_self))
                .filter(Column::Rt.between(range_l, range_r))
                .exec(&txn)
                .await?;

            let delta = rt - self_model.rt;
            let delta_lv = lv - self_model.lv;
            // 自己从负数移动回, 并修改lt rt lv
            Entity::update_many()
                .col_expr(Column::Lt, Expr::col(Column::Lt).mul(-1).add(delta))
                .col_expr(Column::Rt, Expr::col(Column::Rt).mul(-1).add(delta))
                .col_expr(Column::Lv, Expr::col(Column::Lv).add(delta_lv))
                .filter(Column::Lt.lte(-self_model.lt))
                .filter(Column::Rt.gte(-self_model.rt))
                .exec(&txn)
                .await?;
        }
        model.update(&txn).await?;
        txn.commit().await?;
        Ok(())
    }
}
